#include "graph.h"

/*
 * Data types.
 */

struct graph_t {
    LSUP_Term               *uri;           ///< Graph "name" (URI).
    LSUP_Store *            store;          ///< Store handle.
    LSUP_NSMap *            nsm;            /**< Namespace map.
                                              *
                                              * NOTE: This is
                                              * NULL for permanent stores.
                                              */
    void *                  txn;            ///< Store transaction.
};

struct graph_iter_t {
    const LSUP_Graph *      graph;          ///< Parent graph.
    void *                  data;           ///< Iterator state.
    size_t                  ct;             ///< Total lookup matches.
};


/*
 * Static prototypes.
 */

inline static LSUP_rc
graph_iter_next_buffer (LSUP_GraphIterator *it, LSUP_BufferTriple *sspo);

#define ENTRY(a, b) (be) == (LSUP_STORE_##a) ||
static inline bool
check_backend (LSUP_StoreType be)
{ return (BACKEND_TBL false); }
#undef ENTRY


/*
 * Graph API.
 */

LSUP_Graph *
LSUP_graph_new (
        LSUP_Term *uri, const LSUP_StoreType store_type, const char *store_id,
        LSUP_NSMap *nsm, size_t size)
{
    if (UNLIKELY (!LSUP_IS_INIT)) {
        log_error (
                "Environment is not initialized. Did you call LSUP_init()?");
        return NULL;
    }
    if (UNLIKELY (!check_backend (store_type))) return NULL;

    LSUP_Graph *gr;
    MALLOC_GUARD (gr, NULL);

    gr->uri = uri;
    MALLOC_GUARD (gr->store, NULL);

    gr->store->type = store_type;
    gr->store->id = store_id ? strdup (store_id) : NULL;

    switch (gr->store->type) {
#define ENTRY(a, b) \
        case LSUP_STORE_##a: gr->store->sif = &b; break;
BACKEND_TBL
#undef ENTRY
        default:
            log_error ("Not a valid store type: %d", store_type); return NULL;
    };

    // TODO implement custom default context.
    gr->store->data = gr->store->sif->new_fn (store_id, size);

    if (gr->store->sif->features & LSUP_STORE_PERM) gr->nsm = NULL;
    else gr->nsm = nsm ? nsm : LSUP_default_nsm;

    gr->txn = NULL;

    log_debug ("Graph created.");
    return gr;
}


LSUP_rc
LSUP_graph_bool_op(
        const LSUP_bool_op op, const LSUP_Graph *gr1, const LSUP_Graph *gr2,
        LSUP_Graph *res)
{
    LSUP_rc rc = LSUP_NOACTION;
    if (UNLIKELY (
            op != LSUP_BOOL_UNION
            && op != LSUP_BOOL_SUBTRACTION
            && op != LSUP_BOOL_INTERSECTION
            && op != LSUP_BOOL_XOR)) {
        log_error ("Invalid boolean operation: %d.", op);

        return LSUP_VALUE_ERR;
    }

    if (op == LSUP_BOOL_UNION) {
        rc = LSUP_graph_copy_contents (gr1, res);
        PCHECK (rc, fail);
        rc = LSUP_graph_copy_contents (gr2, res);
        PCHECK (rc, fail);

        return LSUP_OK;
    }

    LSUP_Buffer
        *res_sc = LSUP_term_serialize (res->uri),
        *gr1_sc = LSUP_term_serialize (gr1->uri),
        *gr2_sc = LSUP_term_serialize (gr2->uri);
    void *lu1_it, *lu2_it, *add_it;
    LSUP_BufferTriple *sspo = BTRP_DUMMY;
    size_t ct;

    add_it = res->store->sif->add_init_fn (res->store->data, res_sc, gr1->txn);

    if (op == LSUP_BOOL_XOR) {
        // Add triples from gr2 if not found in gr1.
        lu2_it = gr2->store->sif->lookup_fn (
                gr2->store->data, NULL, NULL, NULL, gr2_sc, NULL, gr1->txn);
        while (gr2->store->sif->lu_next_fn (lu2_it, sspo, NULL) == LSUP_OK) {
            lu1_it = gr1->store->sif->lookup_fn (
                    gr1->store->data, sspo->s, sspo->p, sspo->o, gr1_sc,
                    gr1->txn, &ct);
            if (ct > 0)
                res->store->sif->add_iter_fn (add_it, sspo);
            gr1->store->sif->lu_free_fn (lu1_it);
        }
        gr2->store->sif->lu_free_fn (lu2_it);
    }

    lu1_it = gr1->store->sif->lookup_fn (
            gr1->store->data, NULL, NULL, NULL, gr1_sc, gr1->txn, NULL);
    while (gr1->store->sif->lu_next_fn (lu1_it, sspo, NULL) == LSUP_OK) {
        lu2_it = gr2->store->sif->lookup_fn (
                gr2->store->data, sspo->s, sspo->p, sspo->o, gr2_sc,
                gr1->txn, &ct);
        // For XOR and subtraction, add if not found.
        // For intersection, add if found.
        if ((ct == 0) ^ (op == LSUP_BOOL_INTERSECTION))
            res->store->sif->add_iter_fn (add_it, sspo);
        gr2->store->sif->lu_free_fn (lu2_it);
    }
    gr1->store->sif->lu_free_fn (lu1_it);

    res->store->sif->add_done_fn (add_it);
    LSUP_buffer_free (res_sc);
    LSUP_buffer_free (gr1_sc);
    LSUP_buffer_free (gr2_sc);

    return rc;

fail:
    LSUP_graph_free (res);
    return rc;
}


void
LSUP_graph_free (LSUP_Graph *gr)
{
    if (UNLIKELY (!gr)) return;

    LSUP_term_free (gr->uri);
    free (gr->store->id);
    gr->store->sif->free_fn (gr->store->data);
    free (gr->store);

    free (gr);
}


LSUP_Term *
LSUP_graph_uri (const LSUP_Graph *gr) { return gr->uri; }


LSUP_rc
LSUP_graph_set_uri (LSUP_Graph *gr, LSUP_Term *uri)
{
    if (!LSUP_IS_IRI (uri)) {
        log_error ("Term provided is not a IRI.");
        return LSUP_VALUE_ERR;
    }

    LSUP_term_free (gr->uri);
    gr->uri = uri;

    return LSUP_OK;
}


LSUP_NSMap *
LSUP_graph_namespace (const LSUP_Graph *gr)
{
    // If nsm_get_fn is not defined, the store has no own NS map.
    if (!gr->store->sif->nsm_get_fn) return gr->nsm;

    return gr->store->sif->nsm_get_fn (gr->store->data);
}


void
LSUP_graph_set_namespace (LSUP_Graph *gr, LSUP_NSMap *nsm)
{
    if (!gr->store->sif->nsm_get_fn) gr->nsm = nsm;
    else log_warn ("Graph back end has a stored NS map.");
}


size_t
LSUP_graph_size (const LSUP_Graph *gr)
{ return gr->store->sif->size_fn (gr->store->data); }


bool
LSUP_graph_equals (const LSUP_Graph *gr1, const LSUP_Graph *gr2)
{
    LSUP_Graph *res = LSUP_graph_new (NULL, LSUP_STORE_HTABLE, NULL, NULL, 0);
    LSUP_graph_bool_op (LSUP_BOOL_XOR, gr1, gr2, res);
    bool ret = (LSUP_graph_size (res) == 0);

    LSUP_graph_free (res);

    return ret;
}


LSUP_GraphIterator *
LSUP_graph_add_init (LSUP_Graph *gr)
{
    LSUP_GraphIterator *it;
    CALLOC_GUARD (it, NULL);

    LSUP_Buffer *sc = LSUP_term_serialize (gr->uri);

    it->data = gr->store->sif->add_init_fn (gr->store->data, sc, gr->txn);
    LSUP_buffer_free (sc);

    it->graph = gr;

    return it;
}


LSUP_rc
LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo)
{

    LSUP_BufferTriple *sspo = LSUP_triple_serialize (spo);
    if (UNLIKELY (!sspo)) return LSUP_MEM_ERR;
    const LSUP_StoreInt *sif = it->graph->store->sif;

    LSUP_rc rc = sif->add_iter_fn (it->data, sspo);
    PCHECK (rc, finally);

    // Store datatype term permanently if the store supports it.
    if (rc == LSUP_OK && sif->add_term_fn) {
        void *txn;
        for (int i = 0; i < 3; i++) {
            LSUP_Term *term = LSUP_triple_pos (spo, i);
            if (term->type == LSUP_TERM_LITERAL) {
                LSUP_Buffer *ser_dtype = LSUP_term_serialize (term->datatype);
                // Run add_term in the iterator's txn.
                txn = sif->iter_txn_fn ? sif->iter_txn_fn (it->data) : NULL;
                sif->add_term_fn ( it->graph->store->data, ser_dtype, txn);
                LSUP_buffer_free (ser_dtype);
            }
        }
    }


finally:
    LSUP_btriple_free (sspo);

    return rc;
}


void
LSUP_graph_add_done (LSUP_GraphIterator *it)
{
    it->graph->store->sif->add_done_fn (it->data);
    free (it);
}


LSUP_rc
LSUP_graph_add (LSUP_Graph *gr, const LSUP_Triple trp[], size_t *ct)
{
    LSUP_rc rc = LSUP_NOACTION;

    // Initialize iterator.
    LSUP_GraphIterator *it = LSUP_graph_add_init (gr);

    if (ct) *ct = 0;
    // Serialize and insert RDF triples.
    for (size_t i = 0; trp[i].s != NULL; i++) {
        log_trace ("Inserting triple #%lu", i);

        LSUP_rc db_rc = LSUP_graph_add_iter (it, trp + i);

        if (db_rc == LSUP_OK) {
            rc = LSUP_OK;
            if (ct) (*ct)++;
            // A duplicate will return LSUP_NOACTION and not increment the
            // counter.
        }
        if (UNLIKELY (db_rc < 0)) {
            rc = db_rc;
            goto finally;
        }
    }

finally:
    LSUP_graph_add_done (it);

    return rc;
}


LSUP_rc
LSUP_graph_remove (
        LSUP_Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
        const LSUP_Term *o, size_t *ct)
{
    LSUP_rc rc;

    LSUP_Buffer
        *ss = LSUP_term_serialize (s),
        *sp = LSUP_term_serialize (p),
        *so = LSUP_term_serialize (o),
        *sc = LSUP_term_serialize (gr->uri);

    rc = gr->store->sif->remove_fn (
            gr->store->data, ss, sp, so, sc, gr->txn, ct);

    LSUP_buffer_free (ss);
    LSUP_buffer_free (sp);
    LSUP_buffer_free (so);
    LSUP_buffer_free (sc);

    return rc;
}


/**
 * Copy triples from a source graph into a destination one.
 *
 * The destination graph is not initialized here, so the copy is cumulative.
 */
LSUP_rc
LSUP_graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest)
{
    LSUP_rc rc = LSUP_NOACTION;

    LSUP_GraphIterator *it = LSUP_graph_lookup (src, NULL, NULL, NULL, NULL);

    LSUP_Triple spo;

    LSUP_GraphIterator *add_it = LSUP_graph_add_init (dest);
    while (LSUP_graph_iter_next (it, &spo) != LSUP_END) {
        LSUP_rc add_rc = LSUP_graph_add_iter (add_it, &spo);
        LSUP_triple_done (&spo);
        if (LIKELY (add_rc == LSUP_OK)) rc = LSUP_OK;
        else if (add_rc < 0) {
            rc = add_rc;
            break;
        }
    }

    LSUP_graph_add_done (add_it);
    LSUP_graph_iter_free (it);

    return rc;
}


LSUP_GraphIterator *
LSUP_graph_lookup (
        const LSUP_Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
        const LSUP_Term *o, size_t *ct)
{
    LSUP_GraphIterator *it;
    MALLOC_GUARD (it, NULL);

    LSUP_Buffer
        *ss = LSUP_term_serialize (s),
        *sp = LSUP_term_serialize (p),
        *so = LSUP_term_serialize (o),
        *sc = LSUP_term_serialize (gr->uri);

    it->data = gr->store->sif->lookup_fn (
            gr->store->data, ss, sp, so, sc, gr->txn, ct);

    LSUP_buffer_free (ss);
    LSUP_buffer_free (sp);
    LSUP_buffer_free (so);
    LSUP_buffer_free (sc);

    if (UNLIKELY (!it->data)) {
        free (it);
        return NULL;
    }

    it->graph = gr;

    return it;
}


LSUP_rc
LSUP_graph_iter_next (LSUP_GraphIterator *it, LSUP_Triple *spo)
{
    LSUP_Buffer *ss, *sp, *so;
    LSUP_BufferTriple *sspo;
    if (it->graph->store->sif->features & LSUP_STORE_COW) {
        CALLOC_GUARD (ss, LSUP_MEM_ERR);
        CALLOC_GUARD (sp, LSUP_MEM_ERR);
        CALLOC_GUARD (so, LSUP_MEM_ERR);
        sspo = LSUP_btriple_new (ss, sp, so);
    } else {
        // TODO copy-on-retrieval stores. None yet.
    }

    LSUP_rc rc = graph_iter_next_buffer (it, sspo);

    if (rc == LSUP_OK) {
        spo->s = LSUP_term_new_from_buffer (sspo->s);
        if (!spo->s) return LSUP_ERROR;
        spo->p = LSUP_term_new_from_buffer (sspo->p);
        if (!spo->p) return LSUP_ERROR;
        spo->o = LSUP_term_new_from_buffer (sspo->o);
        if (!spo->o) return LSUP_ERROR;
    }

    if (it->graph->store->sif->features & LSUP_STORE_COW) {
        LSUP_btriple_free_shallow (sspo);
    } else {
        // TODO copy-on-retrieval stores. None yet.
    }

    return rc;
}


void
LSUP_graph_iter_free (LSUP_GraphIterator *it)
{
    it->graph->store->sif->lu_free_fn (it->data);
    free (it);
}


bool
LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
{
    LSUP_GraphIterator *it = LSUP_graph_lookup (
            gr, spo->s, spo->p, spo->o, NULL);
    LSUP_Triple *tmp_spo = TRP_DUMMY;
    bool rc = LSUP_graph_iter_next (it, tmp_spo) != LSUP_END;

    LSUP_triple_free (tmp_spo);
    LSUP_graph_iter_free (it);

    return rc;
}


LSUP_rc
LSUP_graph_begin (LSUP_Graph *gr, int flags) {
    if (!(gr->store->sif->features & LSUP_STORE_TXN)) return LSUP_VALUE_ERR;

    return gr->store->sif->txn_begin_fn(gr->store->data, flags, &gr->txn);
}


LSUP_rc
LSUP_graph_commit (LSUP_Graph *gr)
{
    LSUP_rc rc = gr->store->sif->txn_commit_fn (gr->txn);

    if (rc == LSUP_OK) gr->txn = NULL;

    return rc;
}


void
LSUP_graph_abort (LSUP_Graph *gr)
{
    gr->store->sif->txn_abort_fn (gr->txn);
    gr->txn = NULL;
}


/*
 * Static functions.
 */

/** @brief Advance an iterator and return a serialized triple.
 *
 * This is an internal function to pass raw buffers between higher-level
 * functions without serializing and deserializing triples.
 */
inline static LSUP_rc
graph_iter_next_buffer (LSUP_GraphIterator *it, LSUP_BufferTriple *sspo)
{ return it->graph->store->sif->lu_next_fn (it->data, sspo, NULL); }


/**
 * Extern inline definitions.
 */

size_t LSUP_graph_size (const LSUP_Graph *gr);

